import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Random;
import java.util.Scanner;


public class CA 
{

	/**
	 * this is the main function
	 * it loads the input tsv file into an array
	 * perform one of the 3 algorithms on it (depending on the input from the user)
	 * and write the result into a tsv file
	 */
	public static void main(String[] args) 
	{
		String in_file_name;
		String out_file_name;
		String action_case;
		FileReader fr = null;
		BufferedReader br = null; 
		int key;
		// the data array size
		int size = 500;
		int[][] arr = new int[size][size];
		int[][] temp = new int[size][size];
		int len1;
		int len2;
		int cell;
		// the number of iterations
		int counter = 100;
		boolean no_white_cells = false;
		Scanner in = new Scanner(System.in);
		System.out.println("please enter the input file name:");
		in_file_name = in.nextLine();
		File file = new File(in_file_name);
		// the case that the input file doesn't exist
		if(!file.exists())
		{
			System.out.println("file doesn't exist");
		}
		// the case that the input file does exist
		else
		{
			System.out.println("please enter the output file name:");
			out_file_name = in.nextLine();
			System.out.println("please enter the action case number: 1 - for our original algorithm, 2 - for our a-synchronize algorithm, 3 - for our non-deterministic algorithm");
			action_case = in.nextLine();

			try 
			{
				// convert the case number string into an integer
				key = Integer.parseInt(action_case);

				fr = new FileReader(in_file_name);
				br = new BufferedReader(fr);
				String str;
				String[] values = null;
				int index=0;

				// as long as the file has more lines to read
				while ((str = br.readLine()) != null)
				{
					// Separate the data of the tsv file (with tabs)
					values = str.split("	");

					// for each element of the current row in the array "arr"
					for (int j = 0; j < values.length; j++) 
					{
						// update the data from the file
						arr[index][j] = Integer.parseInt(values[j]);
					}
					index++;
				}

				// copying the data of the array "arr" to the array "temp"
				for (int i = 0; i < arr.length; i++) 
				{
					for (int j = 0; j < arr[i].length; j++)
					{
						temp[i][j] = arr[i][j]; 
					}
				}

				len1 = arr.length;

				// go to the correct section (according to the user input)
				switch (key)
				{
				// the case of the original algorithm we made for restoring the image
				case 1:

					// as long as the number of iterations hasn't reached it's limit and there are still white cells in the array (value 0)
					while( counter > 0 && no_white_cells == false)
					{
						// assume no white cells
						no_white_cells = true;
						// for each row in the arr array
						for (int i = 0; i < len1 ; i++) 
						{
							len2 = arr[i].length;
							// for each column in the arr array
							for (int j = 0; j < len2 ; j++) 
							{
								cell = arr[i][j];
								// the case that this is a white cell
								if(cell == 0)
								{
									// activate the algorithm - find the new cell value
									cell = newValue(arr, i, j, true);
									
									// the case that the cell color stayed white in this iteration
									if(cell == 0)
									{
										// update the existence of white cells
										no_white_cells = false;
									}
								}
								// make the changes on another array - a synchronize algorithm
								temp[i][j] = cell;
							}
						}

						// copying the data of the array "temp" to the array "arr"
						for (int i = 0; i < temp.length; i++) 
						{
							for (int j = 0; j < temp[i].length; j++)
							{
								arr[i][j] = temp[i][j]; 
							}
						}

						counter--;
					}


					break;

					// the case of the a-synchronize algorithm
				case 2:

					// as long as the number of iterations hasn't reached it's limit and there are still white cells in the array (value 0)
					while( counter > 0 && no_white_cells == false)
					{
						// assume no white cells
						no_white_cells = true;
						// for each row in the arr array
						for (int i = 0; i < len1 ; i++) 
						{
							len2 = arr[i].length;
							// for each column in the arr array
							for (int j = 0; j < len2 ; j++) 
							{
								cell = arr[i][j];
								// the case that this is a white cell
								if(cell == 0)
								{
									// activate the algorithm - find the new cell value
									cell = newValue(arr, i, j, true);
									
									// the case that the cell color stayed white in this iteration
									if(cell == 0)
									{
										// update the existence of white cells
										no_white_cells = false;
									}
								}
								// update the new cell value - locally
								arr[i][j] = cell;
							}
						}

						counter--;
					}

					break;

					// the case of the non-deterministic algorithm
				case 3:

					// as long as the number of iterations hasn't reached it's limit and there are still white cells in the array (value 0)
					while( counter > 0 && no_white_cells == false)
					{
						// assume no white cells
						no_white_cells = true;
						// for each row in the arr array
						for (int i = 0; i < len1 ; i++) 
						{
							len2 = arr[i].length;
							// for each column in the arr array
							for (int j = 0; j < len2 ; j++) 
							{
								cell = arr[i][j];
								// the case that this is a white cell
								if(cell == 0)
								{
									// activate the non-deterministic (false boolean as input) algorithm - find the new cell value
									cell = newValue(arr, i, j, false);
									
									// the case that the cell color stayed white in this iteration
									if(cell == 0)
									{
										// update the existence of white cells
										no_white_cells = false;
									}
								}
								// make the changes on another array - a synchronize algorithm
								temp[i][j] = cell;
							}
						}

						// copying the data of the array "temp" to the array "arr"
						for (int i = 0; i < temp.length; i++) 
						{
							for (int j = 0; j < temp[i].length; j++)
							{
								arr[i][j] = temp[i][j]; 
							}
						}

						counter--;
					}

					break;

					// the default case - wrong case number
				default:
					System.out.println("case doesn't exist - try again");
					break;
				}


				// the case that the "counter" iterations were not enough to eliminate all white cells
				if(!no_white_cells)
				{
					//last iteration - deleting white cells if exists in "arr"

					// for each row in the arr array
					for (int i = 0; i < len1 ; i++) 
					{
						len2 = arr[i].length;
						// for each column in the arr array
						for (int j = 0; j < len2 ; j++) 
						{
							cell = arr[i][j];
							if(cell == 0)
							{
								// color the cell in black - the background color
								cell = 2;
							}
							arr[i][j] = cell;
						}
					}
					System.out.println("methode didn't converged after: 100");
				}
				// the case that the "counter" iterations were enough to eliminate all white cells
				else
				{
					System.out.println("methode converged after: " + (100-counter));
				}


				// Create the result file 
				FileWriter fstream = new FileWriter(out_file_name);
				BufferedWriter out = new BufferedWriter(fstream);

				// for each row in the arr array
				for (int ind1 = 0; ind1 < arr.length; ind1++) 
				{
					// for each column in the arr array
					for (int ind2 = 0; ind2 < arr[ind1].length; ind2++) 
					{
						// the case that this is not the last column in the row
						if(ind2 != (arr[ind1].length - 1))
						{
							// write the data into the file
							out.write(String.valueOf(arr[ind1][ind2]) + "	");
						}
						// the case that this is the last column in the row
						else
						{
							// write the data into the file
							out.write(String.valueOf(arr[ind1][ind2]) + '\n');
						}
					}
				}

				out.close();
				fr.close();
				br.close();
			}

			// the case that the user hasn't entered a legal number in the action case input
			catch (NumberFormatException ne)
			{
				System.out.println("you havn't entered a legal number - try again");
			}
			// another problem
			catch (Exception e) 
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * this function counts the number of black cells neighbor (value 2) and orange ones (value 1)
	 * and returns the new cell value accordingly
	 * @param arr - the data array
	 * @param i - the cell row
	 * @param j - the cell column
	 * @param det - true if we are running a deterministic algorithm and false of not
	 * @return - the new cell value
	 */
	private static int newValue(int[][] arr, int i, int j, boolean det) 
	{
		// Initialize the number of black neighbors
		int black_count = 0;
		// Initialize the number of orange neighbors
		int orange_count = 0;
		int row = arr.length - 1;
		int col = arr[i].length - 1;
		int result;

		// the case that this cell don't have all of the 8-usual neighbors (an edge cell)
		if(i==0 || i==row || j==0 || j==col)
		{
			// the case that this cell is in the first row
			if(i==0)
			{
				// the case that this cell is in the first column
				if(j==0)
				{
					//updating the correct neighbor counter (if needed)
					result = update(i, j+1, arr);
					if(result == 1) orange_count++;
					else if(result == 2) black_count++;

					//updating the correct neighbor counter (if needed)
					result = update(i+1, j, arr);
					if(result == 1) orange_count++;
					else if(result == 2) black_count++;

					//updating the correct neighbor counter (if needed)
					result = update(i+1, j+1, arr);
					if(result == 1) orange_count++;
					else if(result == 2) black_count++;
				}
				// the case that this cell is not in the first row
				else
				{
					// the case that this cell is in the last column
					if(j==col)
					{
						//updating the correct neighbor counter (if needed)
						result = update(i, j-1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j-1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;
					}
					// the case that this cell is not in the last column
					else
					{
						//updating the correct neighbor counter (if needed)
						result = update(i, j-1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i, j+1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j+1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j-1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;
					}
				}
			}
			// the case that this cell is not in the first row
			else
			{
				// the case that this cell is in the last row
				if(i==row)
				{
					// the case that this cell is in the first column
					if(j==0)
					{
						//updating the correct neighbor counter (if needed)
						result = update(i-1, j, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i, j+1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i-1, j+1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;
					}
					// the case that this cell is not in the first column
					else
					{
						// the case that this cell is in the last column
						if(j==col)
						{
							//updating the correct neighbor counter (if needed)
							result = update(i-1, j-1, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;

							//updating the correct neighbor counter (if needed)
							result = update(i-1, j, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;

							//updating the correct neighbor counter (if needed)
							result = update(i, j-1, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;

						}
						// the case that this cell is not in the last column
						else
						{
							//updating the correct neighbor counter (if needed)
							result = update(i-1, j-1, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;

							//updating the correct neighbor counter (if needed)
							result = update(i-1, j, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;

							//updating the correct neighbor counter (if needed)
							result = update(i, j-1, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;

							//updating the correct neighbor counter (if needed)
							result = update(i, j+1, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;

							//updating the correct neighbor counter (if needed)
							result = update(i-1, j+1, arr);
							if(result == 1) orange_count++;
							else if(result == 2) black_count++;
						}
					}
				}
				// the case that this cell is not in the last row
				else
				{
					// the case that this cell is in the first column
					if(j==0)
					{
						//updating the correct neighbor counter (if needed)
						result = update(i-1, j, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i, j+1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j+1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i-1, j+1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;
					}
					// the case that this cell is not in the first column - it's in the last column
					else
					{
						//updating the correct neighbor counter (if needed)
						result = update(i-1, j-1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i-1, j, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i, j-1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;

						//updating the correct neighbor counter (if needed)
						result = update(i+1, j-1, arr);
						if(result == 1) orange_count++;
						else if(result == 2) black_count++;
					}
				}
			}
		}
		// the case that this cell have all of the 8-usual neighbors
		else
		{
			//updating the correct neighbor counter (if needed)
			result = update(i-1, j-1, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;

			//updating the correct neighbor counter (if needed)
			result = update(i-1, j, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;

			//updating the correct neighbor counter (if needed)
			result = update(i, j-1, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;

			//updating the correct neighbor counter (if needed)
			result = update(i, j+1, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;

			//updating the correct neighbor counter (if needed)
			result = update(i+1, j, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;

			//updating the correct neighbor counter (if needed)
			result = update(i+1, j+1, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;

			//updating the correct neighbor counter (if needed)
			result = update(i-1, j+1, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;

			//updating the correct neighbor counter (if needed)
			result = update(i+1, j-1, arr);
			if(result == 1) orange_count++;
			else if(result == 2) black_count++;
		}

		// calling the function that will calculate the new cell value
		return get_bigger(black_count, orange_count, det);
	}

	/**
	 * this function calculates the new cell value according to the higher number
	 * of neighbors of a specific color (in case of a tie the cell will stay white unless
	 * this is the non-deterministic case or the tie is between 4 black cells and 4 orange ones)
	 * @param black_count - the number of black neighbors
	 * @param orange_count - the number of orange neighbors
	 * @param det - true if we are running a deterministic algorithm and false of not
	 * @return - the new cell value
	 */
	private static int get_bigger(int black_count, int orange_count, boolean det) 
	{
		// the case that the number of orange neighbors is higher
		if(orange_count > black_count)
		{
			return 1;
		}
		// the case that the number of orange neighbors isn't the highest
		else
		{
			// the case that the number of black neighbors is higher
			if(orange_count < black_count)
			{
				return 2;
			}
			// the case that the number of orange neighbors is equal to the number of black ones
			else
			{
				// the case that there is a tie between the black and orange neighbors - an equal tie (4 v.s 4) - meaning this is a shape border cell
				if(orange_count == 4)
				{
					// return the background color in this case
					return 2;
				}
				// the case that there is a normal tie between the black and orange neighbors
				else
				{
					// the case that this is the run of the deterministic algorithm
					if(det)
					{
						// keep this cell white in this iteration
						return 0;
					}
					// the case that this is the run of the non-deterministic algorithm
					else
					{
						// the case that this cell has no colored neighbors
						if(orange_count == 0)
						{
							// keep this cell white in this iteration
							return 0;
						}
						// the case that this cell has some colored neighbors
						else
						{
							Random r = new Random();
							// create a random number between 0-3
							int num = r.nextInt(4);
							// the case that the random number is 0 (25%)
							if(num == 0)
							{
								// return the shape color
								return 1;
							}
							// the case that the random number is not 0 (75%)
							else
							{
								// return the background color
								return 2;
							}
						}
					}
				}
			}
		}
	}

	/**
	 * this function return the value of the i,j cell (was built for generic count reasons)
	 * @param i - the cell row
	 * @param j - the cell column
	 * @param arr - the array of data
	 * @return the cell value
	 */
	private static int update(int i, int j, int[][] arr)
	{
		// the case this cell is orange
		if(arr[i][j] == 1)
		{
			return 1;
		}
		// the case this cell is not orange
		else
		{
			// the case this cell is black
			if(arr[i][j] == 2)
			{
				return 2;
			}
			// the case this cell is white
			else
			{
				return 0;
			}
		}
	}
}
